// a class for drawing
#include "drawclass.hpp"

/// ---------------- utils -----------------------
Cdrawing::Cdrawing(point* pt, uint s, wire* wr, uint ws):
	Apoint(pt), size(s), Awire(wr), wsize(ws), is_act(NULL) {
	win_min = minvec();
	win_max = maxvec();
	win_asp = aspect();
	stime = glfwGetTime();
	frame = 0;
}

Cdrawing::~Cdrawing() {
	kill();
}

void Cdrawing::kill() {
	glfwTerminate();
}

// IF class points are NULL - overwrite, ELSE swap parameters
void Cdrawing::swap_points(point* &P, uint &s) {
	if(Apoint != NULL) {
		point* tmp = Apoint;
		Apoint = P;
		P = tmp;
		s ^= size;
		size ^= s;
		s ^= size;
	}
	else {
		size = s;
		Apoint = P;
	}
}

// IF class wires are NULL - overwrite, ELSE swap parameters
void Cdrawing::swap_wires(wire* &W, uint &ws) {
	if(Awire != NULL) {
		wire* tmp = Awire;
		Awire = W;
		W = tmp;
		ws ^= wsize;
		wsize ^= ws;
		ws ^= wsize;
	}
	else {
		wsize = ws;
		Awire = W;
	}
}

bool Cdrawing::exit() {
	return glfwGetKey(GLFW_KEY_ESC) || !glfwGetWindowParam(GLFW_OPENED);
}

void Cdrawing::printw(bool f) {
	if (!f)
		for (uint i = 0; i < wsize; i++) {
		printf("this is wire nr: %u, it's point 1 is:\n", i+1);
		printp(*Awire[i].ps);
		printf("and other is:\n");
		printp(*Awire[i].pe);
	}
	else {
		FILE* out;
		for (uint i = 0; i < wsize; i++) {
			out = freopen("wire_pts_test.txt", "a", stdout);
			printf("this is wire nr: %u, it's point 1 is:\n", i+1);
			fclose (out);
			printp(*Awire[i].ps, 1);
			out = freopen("wire_pts_test.txt", "a", stdout);
			printf("and other is:\n");
			fclose (out);
			printp(*Awire[i].pe, 1);
		}
	}
}
void Cdrawing::printp(const point& pt, bool f) {
	if (!f) {
		printf("pos: (%f, %f), ", pt.pos.x, pt.pos.y);
		printf("dpos: (%f, %f), ", pt.dpos.x, pt.dpos.y);
		printf("vel: (%f, %f), ", pt.vel.x, pt.vel.y);
		printf("F: (%f, %f), ", pt.fo.x, pt.fo.y);
		printf("mass: (%f)\n", pt.mass);
	}
	else {
		FILE* out = freopen("wire_pts_test.txt", "a", stdout);
		printf("pos: (%f, %f), ", pt.pos.x, pt.pos.y);
		printf("dpos: (%f, %f), ", pt.dpos.x, pt.dpos.y);
		printf("vel: (%f, %f), ", pt.vel.x, pt.vel.y);
		printf("F: (%f, %f), ", pt.fo.x, pt.fo.y);
		printf("mass: (%f)\n", pt.mass);
		fclose (out);
	}
}
/*
void Cdrawing::savew() {
	FILE* out;
	out = freopen("wiring.txt", "w", stdout);
	printf("wires: %d", wsize);
	for (uint i = 0; i < wsize; i++) {
	    printf("\n%f: %
	}
	fclose (out);
}*/

void Cdrawing::savep() {
	FILE* out = freopen("imputdot.txt", "w", stdout);
	printf("points: %d", size);
	point pt;
	for(uint i = 0; i < size; i++) {
	    pt = Apoint[i];
		printf("\n(%f, %f) ", pt.pos.x, pt.pos.y);
		printf("(%f, %f) ", pt.vel.x, pt.vel.y);
		printf("(%f, %f) ", pt.fo.x, pt.fo.y);
		printf("m%f", pt.mass);
	}
	fclose (out);
}

// gets max point koordinates +EXTRA in a vec
vec Cdrawing::maxvec() {
	vec tmp;
	if(size == 0) printf("0 size point array\n");
	tmp.x = Apoint[0].pos.x; tmp.y = Apoint[0].pos.y;
	for(uint i = 1; i < size; i++) {
		if(tmp.x - Apoint[i].pos.x < 0)
			tmp.x = Apoint[i].pos.x;
		if(tmp.y - Apoint[i].pos.y < 0)
			tmp.y = Apoint[i].pos.y;
	}
	tmp += EXTRA;
	printf("%f %f\n", tmp.x, tmp.y);
	return tmp;
}

// gets min point koordinates -EXTRA in a vec
vec Cdrawing::minvec() {
	vec tmp;
	if(size == 0) printf("0 size point array\n");
	tmp.x = Apoint[0].pos.x; tmp.y = Apoint[0].pos.y;
	for(uint i = 1; i < size; i++) {
		if(tmp.x - Apoint[i].pos.x > 0)
			tmp.x = Apoint[i].pos.x;
		if(tmp.y - Apoint[i].pos.y > 0)
			tmp.y = Apoint[i].pos.y;
	}
	tmp -= EXTRA;
	printf("%f %f\n", tmp.x, tmp.y);
	return tmp;
}

// returns x and y aspect ratio in vec
vec Cdrawing::aspect() {
	vec result;
	result = win_max - win_min;
	win_wh.x = result.x/WIDTH;
	win_wh.y = result.y/HEIGH;
	result.x /= WIDTH /RAT;
	result.y /= HEIGH /RAT;
	return result;
}
void Cdrawing::save_on_S() {
    if(but>>5&1) {
        if(!glfwGetKey('S')) {
            but -= 1<<5;
        }
        else {
            savep();
        }
    }
    else {
        if(glfwGetKey('D')) {
            but += 1<<5;
        }
        savep();
    }
}

// except for pos and vel
void Cdrawing::reset_point(point& p) {
    p.dpos(0,0);
    //p.vel(0,0);
    p.dvel(0,0);
    p.fo(0,0);
}

// makes current length spring's rest length when 'd' pressed
// uses global 'but'
void Cdrawing::deform() {
    if(but>>4&1) {
        if(!glfwGetKey('D')) {
            but -= 1<<4;
        }
        else {
            for(uint i = 0; i < wsize; i++) {
            Awire[i].rest = Awire[i].len.len();
            }
            for(uint i = 0 ; i < size; i++) {
                reset_point(Apoint[i]);
            }
        }
    }
    else {
        if(glfwGetKey('D')) {
            but += 1<<4;
        }
    }
}
/// ----------------- end of utils ---------------


/// ----------------- mouse ----------------------
// returns mouse button pressed; use shifting:
// left - >>0, right - >>1, middle - >>2
// uses global 'but'
void Cdrawing::mouse_but() {

    if(glfwGetMouseButton(GLFW_MOUSE_BUTTON_LEFT)) {
        if(!(but&1)) {
            but += 1;
        }
    }
    else {
        if(but&1) {
            but -= 1;
        }
    }
    if(glfwGetMouseButton(GLFW_MOUSE_BUTTON_RIGHT)) {
        if(!(but>>1&1)) {
            but += 1<<1;
        }
    }
    else {
        if(but>>1&1) {
            but -= 1<<1;
        }
    }
    if(glfwGetMouseButton(GLFW_MOUSE_BUTTON_MIDDLE)) {
        if(!(but>>2&1)) {
            but += 1<<2;
        }
    }
    else {
        if(but>>2&1) {
            but -= 1<<2;
        }
    }

    // optional printing in console for checking
    printf("%d:%d:%d ", but>>0&1, but>>2&1, but>>1&1);
}

// koords in spring field
void Cdrawing::mouse_pos() {

    intvec mouse;
    glfwGetMousePos(&mouse.x, &mouse.y);
    win_mouseold = win_mouse;
    win_mouse.x = (mouse.x * win_wh.x + win_min.x);
    win_mouse.y = ((HEIGH - mouse.y) * win_wh.y + win_min.y);

    // optional printing in console for checking
    printf("x - %f, y - %f;\n", win_mouse.x, win_mouse.y);
}

// if mouse is over dot's square, return 1, else - 0
bool Cdrawing::is_mouse_in(const vec& pos) {
    if(pos.x - win_asp.x < win_mouse.x && pos.x + win_asp.x > win_mouse.x) {
        if(pos.y - win_asp.y < win_mouse.y && pos.y + win_asp.y > win_mouse.y) {
            return 1;
        }
    }
    return 0;
}

// dot dragging routine, checks whenever selected point have
// to follow mouse pos' change works when left button pressed
void Cdrawing::dot_drag(point& p) {
    if(!is_act) {
        if(is_mouse_in(p.pos)) {
            if(but&1) {
                glColor3f(1.0f, 0.0f, 0.0f);
                is_act = &(p.act);
                *is_act = 1;
            }
            else {
                glColor3f(0.0f, 1.0f, 0.0f);
            }
        }
        else {
            glColor3f(0.0f, 0.0f, 1.0f);
        }
    }
    else {
        if(but&1) {
            if(p.act) {
                p.pos += win_mouse - win_mouseold;
                glColor3f(1.0f, 0.0f, 0.0f);
				printf("drag on");
            }
            else {
                glColor3f(0.0f, 0.0f, 1.0f);
            }
        }
        else {
            *is_act = 0;
            is_act = NULL;
        }
    }
}

void Cdrawing::dot_drag2() {
	point* p = tree.find(win_mouse, win_min, win_max);
	if(!is_act) {
		if(p) {
	//		if(is_mouse_in(p->pos)) {
				if(but&1) {
					is_act = &(p->act);
					*is_act = 1;
				}
	//		}
		}
	}
	else {
		if(but&1) {
		    if(p) p->pos += win_mouse - win_mouseold;
			}
		else {
			*is_act = 0;
			is_act = NULL;
		}
	}
}

// with 'b' pressed all point comes nearer to mouse by half distance
void Cdrawing::bang() {
	if(but>>3&1) {
        if(!glfwGetKey('B')) {
            but -= 1<<3;
        }
	}
	else {
	    if(glfwGetKey('B')) {
	        but += 1<<3;
	        for(uint i = 0; i < size; i++) {
	            Apoint[i].pos = (Apoint[i].pos + win_mouse)/2.0;
                reset_point(Apoint[i]);
	        }
	    }
	}
}

/// -------------- end of mouse ------------------


/// -------------- when to render ----------------
// returns true (1)  to render
bool Cdrawing::time() {
	ctime = glfwGetTime();
	if((((ctime - stime) - floor(ctime - stime)) - FRAMERATE) < 0)
		return 0;
	else return 1;
}
/// --------------- end of render time -----------


/// ------------------- inition ------------------
// screen initialization, returns false if failed
// vmin and vmax values are a must so far
bool Cdrawing::screen_init(uint width, uint height,
	char name[], bool fullscreen) {
	glfwInit();
	if(!fullscreen) {
		if(!glfwOpenWindow(width, height, 8, 8, 8, 0, 0, 0,
			GLFW_WINDOW)) {
			glfwTerminate();
			printf("failed screen");
			return 0;
		}
	}
	else if(!glfwOpenWindow(width, height, 8, 8, 8, 0, 0, 0,
		GLFW_FULLSCREEN)){
		glfwTerminate();
		return 0;
	}
//	glClearDepth(1.0f);
	printf("screen - ok");
	glfwSetWindowTitle(name);
//	height = height < 1 ? 1 : height;
	glDisable(GL_DEPTH_TEST);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

// to set 2D plane with coordinates the way I want
// also zooms view to fit all springs in it
	glOrtho(win_min.x, win_max.x, win_min.y, win_max.y, 0.0f, 1.0f);
	ctime = 0.0;
//if init succeeded returns true
	return 1;
}

// class init: less parameters
bool Cdrawing::init(uint width, uint height, bool f) {
	if(!screen_init(width, height, "Screaker", f)) {
		printf("init - NOT ok");
		return 0;
	}
// return true if everything is ok
	return 1;
}
/// ------------ end of inition ------------------


/// ------------------- drawing ------------------
// draws springs - lines
void Cdrawing::draw_lines() {
	uint i;
	vec st, ed;
	glBegin(GL_LINES);
	for (i = 0; i < wsize; i++) {
		st = Awire[i].ps->pos;
		ed = Awire[i].pe->pos;
		// new colors - changing colors
		Awire[i].length();
		col = (Awire[i].len.len() - Awire[i].rest)/Awire[i].rest;
		//printf("col - %f, %f ", Awire[i].len.x, Awire[i].len.y);
		glColor3f(1.0f - col, fabs(col), 0.0f - col);
		// old colors
		//glColor3f(1.0f, 0.0f, 0.0f);
		glVertex2f(st.x, st.y);
		glVertex2f(ed.x, ed.y);
	}
	glEnd();
}

// draws dots
void Cdrawing::draw_dots() {
	uint i;
	vec p;
	glBegin(GL_QUADS);
	//dot_drag2();
	for (i = 0; i < size; i++) {

 		dot_drag(Apoint[i]);

		p = Apoint[i].pos;
		/*
		if(is_act && *is_act) {
			glColor3f(1.0f, 0.0f, 0.0f);
		}
		else ;
		glColor3f(0.0f, 0.0f, 1.0f);
		*/
        /* ------- square -----------
		  less confusing when draggin */
        glVertex2f(p.x - win_asp.x, p.y + win_asp.y);
        glVertex2f(p.x + win_asp.x, p.y + win_asp.y);
        glVertex2f(p.x + win_asp.x, p.y - win_asp.y);
        glVertex2f(p.x - win_asp.x, p.y - win_asp.y);
        /* ------- diamond ----------
		glVertex2f(p.x - win_asp.x, p.y);
		glVertex2f(p.x, p.y + win_asp.y);
		glVertex2f(p.x + win_asp.x, p.y);
		glVertex2f(p.x, p.y - win_asp.y);
		*/
	}
	glEnd();
}


void Cdrawing::draw() {
	//float fps;
	//char* title;
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	//Ox and Oy axis  -- used as reference for moving
	glBegin(GL_LINES);
		glColor3f(1.0f, 1.0f, 1.0f);
		glVertex2f(win_min.x, 0.0f);
		glVertex2f(win_max.x, 0.0f);
		glVertex2f(0.0f, win_min.y);
		glVertex2f(0.0f, win_max.y);
	glEnd();
	printf("check 1");

    if(tree.check) tree.kill();
	printf("check 4");
    tree.make(Apoint, win_min, win_max, 0);
	printf("check 2");
	mouse_pos();
	// press 'b' and everything will chase mouse
	bang();
	// press 'd' and current spring length becomes their rest length
	deform();

	// back to drawing
	draw_lines();
	draw_dots();
	stime = glfwGetTime();
	save_on_S();

	/*if(stime - ctime) {
		fps = (float)frame/(stime-ctime);
		sprintf(title, "screaker (%0.1f)", fps);
		glfwSetWindowTitle(title);
		ctime = stime;
		frame = 0;
	}*/

	glfwSwapInterval(1);
	glfwSwapBuffers();
	frame++;
}

